home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1998 January: Mac OS SDK / Dev.CD Jan 98 SDK2.toast / Development Kits (Disc 2) / QuickTime™ VR 2.0 SDK / QTVR C⁄C++ Runtime API / Sample Code / VRShell Sample Code / VRSpeech / TestFunctions.c < prev    next >
Encoding:
Text File  |  1997-05-22  |  16.1 KB  |  670 lines  |  [TEXT/MPCC]

  1. //
  2. //    File:        TestFunctions.c
  3. //
  4. //    Contains:    Speech recognition support for QuickTime VR movies.
  5. //
  6. //    Written by:    Tim Monroe
  7. //
  8. //    Copyright:    © 1994-1996 by Apple Computer, Inc., all rights reserved.
  9. //
  10. //    Change History (most recent first):
  11. //
  12. //       <1>         12/05/96    rtm        ported earlier speech recognition support functions to VRShell
  13. //       
  14. //
  15. // TO DO:
  16. // *get transition swing working, for smoother transitions
  17. // *on a mousedown event in QTVR window, stop spinning immediately??
  18. // *implement node navigation
  19. // *presumably, spinning should be on a per-instance basis
  20.  
  21.  
  22. // header files
  23. #include "TestFunctions.h"
  24. #include "LMSpeech.h"        // refcons of language model elements
  25.  
  26. #include "MacFramework.h"
  27. #include "QTVRUtilities.h"
  28.  
  29. // system headers
  30. #include <Windows.h>
  31. #include <Resources.h>
  32. #include <GestaltEqu.h>
  33. #include <TextUtils.h>
  34. #include <FixMath.h>
  35. #include <Speech.h>
  36.  
  37. extern Boolean gHasSpeechRec;
  38.  
  39. // declare global variables
  40. SRRecognitionSystem        gSystem;
  41. SRRecognizer            gRecognizer;
  42. SRLanguageModel            gVRLM;
  43. MyTMTask                 gTMTaskRec;
  44. TimerUPP                gTimerTaskUPP;
  45. Boolean                    gDoSpeechTask;        // is a speech-initiated periodical active?
  46.  
  47. // local function prototypes
  48. pascal void                MySpinTask (MyTMTaskPtr theTaskPtr);
  49. void                    InstallSpeechFeedbackRoutine (QTVRInstance theInstance);
  50. pascal void                SpeechFeedbackRoutine (QTVRInstance theInstance, QTVRInterceptPtr theMsg, SInt32 refcon, Boolean *cancel);
  51.  
  52.  
  53. /////
  54. //
  55. // SpeechInit
  56. // initialize speech recognition, if it's available
  57. //
  58. /////
  59.  
  60. void SpeechInit (void)
  61. {
  62.     OSErr            myErr;
  63.     long            myResponse;
  64.     unsigned long    myParam;
  65.     
  66.     myErr = Gestalt(gestaltSpeechRecognitionVersion, &myResponse);
  67.     // version must be at least 1.5.0 to support SRM API used here
  68.     if (!myErr)
  69.         if (myResponse >= 0x00000150)
  70.             gHasSpeechRec = true;
  71.         
  72.     if (!gHasSpeechRec)
  73.         return;
  74.         
  75.     // open a recognition system
  76.     if (!myErr)
  77.         myErr = SROpenRecognitionSystem(&gSystem, kSRDefaultRecognitionSystemID);
  78.     
  79.     // set recognition system properties
  80.     // we want the user-selected feedback and listening modes
  81.     if (!myErr) {
  82.         short myModes = kSRHasFeedbackHasListenModes;
  83.         myErr = SRSetProperty(gSystem, kSRFeedbackAndListeningModes, &myModes, sizeof(myModes));
  84.     }
  85.  
  86.     // create a recognizer with default speech source
  87.     if (!myErr)
  88.         myErr = SRNewRecognizer(gSystem, &gRecognizer, kSRDefaultSpeechSource);
  89.                         
  90.     // set recognizer properties
  91.     if (!myErr) {
  92.         // we'd like *our* top-level LM to be the only one active;
  93.         Boolean myBlock = true;
  94.         myErr = SRSetProperty(gRecognizer, kSRBlockBackground, &myBlock, sizeof(myBlock));
  95.         
  96.         // we want to receive speech-begun and recognition-done Apple events
  97.         myParam = kSRNotifyRecognitionBeginning | kSRNotifyRecognitionDone;
  98.         myErr = SRSetProperty(gRecognizer, kSRNotificationParam, &myParam, sizeof(myParam));
  99.     }
  100.  
  101.     // install Apple event handlers
  102.     if (!myErr) {
  103.         myErr = AEInstallEventHandler(kAESpeechSuite, kAESpeechDetected, NewAEEventHandlerProc(HandleSpeechBegunAppleEvent), 0, false);
  104.         myErr = AEInstallEventHandler(kAESpeechSuite, kAESpeechDone, NewAEEventHandlerProc(HandleSpeechDoneAppleEvent), 0, false);
  105.     }
  106.             
  107.     // get our language models
  108.     if (!myErr)
  109.         myErr = ReadLanguageModelsFromResource();
  110.  
  111.     // install initial language model
  112.     if (!myErr)
  113.         myErr = SRSetLanguageModel(gRecognizer, gVRLM);
  114.  
  115.     // have the recognizer start processing sound
  116.     if (!myErr)
  117.         myErr = SRStartListening(gRecognizer);    
  118.     
  119.     // allocate our spinning task
  120.     gTimerTaskUPP = NewTimerProc(MySpinTask);
  121. }
  122.  
  123.  
  124. /////
  125. //
  126. // SpeechStop
  127. // shut down speech recognition
  128. //
  129. /////
  130.  
  131. void SpeechStop (void)
  132. {
  133.     //stop any spinning before we exit
  134.     if (IsSpinning()) {
  135.         StopSpinning();
  136.     }
  137.  
  138.     //release any existing language models
  139.     SRReleaseObject(gVRLM);
  140.     
  141.     //shut down speech recognition
  142.     SRStopListening(gRecognizer);        // stop processing incoming sound
  143.     SRReleaseObject(gRecognizer);        // balance SRNewRecognizer call
  144.     SRCloseRecognitionSystem(gSystem);    // balance SROpenRecognitionSystem call
  145. }
  146.  
  147.  
  148. /////
  149. //
  150. // HandleSpeechBegunAppleEvent
  151. // handle speech-begun events; currently this does nothing interesting; 
  152. // in the future, we'll use it to adjust our language models according to context
  153. //
  154. /////
  155.  
  156. pascal OSErr HandleSpeechBegunAppleEvent (AppleEvent *theAEevt, AppleEvent* reply, long refcon)
  157. {
  158. #pragma unused(reply, refcon)
  159.  
  160.     long                myActualSize;
  161.     DescType            myActualType;
  162.     OSErr                myErr = noErr, recErr = noErr;
  163.     SRRecognizer        myRec;
  164.     
  165.     // get status and recognizer
  166.     myErr = AEGetParamPtr(theAEevt, keySRSpeechStatus, typeShortInteger, &myActualType, (Ptr)&recErr, sizeof(recErr), &myActualSize);
  167.     if (!myErr && !recErr) {
  168.         myErr = AEGetParamPtr(theAEevt, keySRRecognizer, typeSRRecognizer, &myActualType, (Ptr)&myRec, sizeof(myRec), &myActualSize);
  169.     }
  170.     
  171.     // better bail if I couldn't get status or recognizer!
  172.     if (myErr)
  173.         if (!myRec)
  174.             return(myErr);
  175.             
  176.     // here is where we would adjust LMs according to context
  177.     
  178.     // now tell the recognizer to continue
  179.     myErr = SRContinueRecognition(myRec);
  180.     return(myErr);
  181. }
  182.  
  183.  
  184. /////
  185. //
  186. // HandleSpeechDoneAppleEvent
  187. // handle recognition-done Apple event.
  188. //
  189. /////
  190.  
  191. pascal OSErr HandleSpeechDoneAppleEvent (AppleEvent *theAEevt, AppleEvent* reply, long refcon)
  192. {
  193. #pragma unused(reply, refcon)
  194.  
  195.     long                    myActualSize;
  196.     DescType                myActualType;
  197.     OSErr                    myErr = 0, recErr = 0;
  198.     SRRecognitionResult        myRecResult;    
  199.     SRLanguageModel            myResultLM;
  200.     Size                    myLen;
  201.     QTVRInstance            myInstance;
  202.     long                    myDir;            // the direction we're moving
  203.     long                    myAmt;            // the amount we're moving
  204.     long                    myCount;
  205.     
  206.     // get recognition result status
  207.     myErr = AEGetParamPtr(theAEevt, keySRSpeechStatus, typeShortInteger, &myActualType, (Ptr)&recErr, sizeof(recErr), &myActualSize);
  208.  
  209.     // get recognition result
  210.     if (!myErr && !recErr)
  211.         myErr = AEGetParamPtr(theAEevt, keySRSpeechResult, typeSRSpeechResult, &myActualType, (Ptr)&myRecResult, sizeof(myRecResult), &myActualSize);
  212.                     
  213.     // better bail if I couldn't get the recognition result!
  214.     if (myErr)
  215.         return(myErr);
  216.  
  217.     // get the current movie
  218.     myInstance = GetQTVRInstanceFromFrontWindow();
  219.     if (!myInstance) 
  220.         return(invalidMovie);
  221.     
  222.     // extract the language model from the recognition result...
  223.     myLen = sizeof(myResultLM);
  224.     myErr = SRGetProperty(myRecResult, kSRLanguageModelFormat, &myResultLM, &myLen);
  225.         
  226.     if (!myErr) {
  227.         long                    myRefCon;
  228.         SRLanguageObject        myItem1;
  229.         SRLanguageObject        myItem2;
  230.         SRPath                    myPath;
  231.         
  232.         // ...and then get its refcon, so we know which one it is
  233.         myLen = sizeof(myRefCon);
  234.         myErr = SRGetProperty(myResultLM, kSRRefCon, &myRefCon, &myLen);
  235.             
  236.         // at this point, the refcon better be kVRAllCmd; otherwise, bail
  237.         if (myRefCon != kVRAllCmd)
  238.             return(kSRModelMismatch);
  239.         
  240.         // get the one and only item in the top-level language model, a path
  241.         myErr = SRGetIndexedItem(myResultLM, &myPath, 0);
  242.         myLen = sizeof(myRefCon);
  243.         myErr = SRGetProperty(myPath, kSRRefCon, &myRefCon, &myLen);
  244.  
  245.         switch (myRefCon) {
  246.             case kMoveDirAndDeg:    // these two parse similarly
  247.             case kMoveDirAndRad:
  248.                     myErr = SRGetIndexedItem(myPath, &myItem1, 1);        // it's a one-item LM!
  249.                     myErr = SRGetIndexedItem(myItem1, &myItem2, 0);        // so get the enclosed item
  250.                     myLen = sizeof(myRefCon);
  251.                     myErr = SRGetProperty(myItem2, kSRRefCon, &myDir, &myLen);
  252.                     myErr = SRGetIndexedItem(myPath, &myItem1, 2);        // it's a one-item LM!
  253.                     myErr = SRGetIndexedItem(myItem1, &myItem2, 0);        // so get the enclosed item
  254.                     myLen = sizeof(myRefCon);
  255.                     myErr = SRGetProperty(myItem2, kSRRefCon, &myAmt, &myLen);
  256.                     if (myRefCon == kMoveDirAndDeg)
  257.                         GoDirByDegrees(myInstance, myDir, myAmt);
  258.                     else
  259.                         GoDirByRadians(myInstance, myDir, myAmt);
  260.                     break;
  261.                 break;
  262.             
  263.             case kMoveToNode:
  264.                 break;
  265.                 
  266.             case kZoomDir:
  267.                 myErr = SRCountItems(myPath, &myCount);
  268.                 myErr = SRGetIndexedItem(myPath, &myItem1, myCount - 1);    // it's a one-item LM!
  269.                 myErr = SRGetIndexedItem(myItem1, &myItem2, 0);                // so get the enclosed item
  270.                 myLen = sizeof(myRefCon);
  271.                 myErr = SRGetProperty(myItem2, kSRRefCon, &myDir, &myLen);
  272.                 ZoomInOrOut(myInstance, myDir);
  273.                 break;
  274.                 
  275.             case kSpinStart:
  276.                 myErr = SRGetIndexedItem(myPath, &myItem1, 1);            // it's a one-item LM!
  277.                 myErr = SRGetIndexedItem(myItem1, &myItem2, 0);            // so get the enclosed item
  278.                 myLen = sizeof(myRefCon);
  279.                 myErr = SRGetProperty(myItem2, kSRRefCon, &myDir, &myLen);
  280.                 StartSpinning(myInstance, myDir);
  281.                 break;
  282.                 
  283.             case kSpinStop:
  284.                 StopSpinning();
  285.                 break;
  286.                 
  287.             default:
  288.                 break;
  289.         }            
  290.         
  291.         SRReleaseObject(myItem1);
  292.         SRReleaseObject(myItem2);
  293.         SRReleaseObject(myPath);
  294.     }
  295.                         
  296.     // release recognition result, since we are done with it
  297.     SRReleaseObject(myRecResult);
  298.     SRReleaseObject(myResultLM);
  299.  
  300.     return(myErr);
  301. }
  302.  
  303.  
  304. /////
  305. //
  306. // ReadLanguageModelsFromResource
  307. // get our language model(s); here we read a pre-rolled model from a resource
  308. //
  309. /////
  310.  
  311. OSErr ReadLanguageModelsFromResource (void)
  312. {
  313.     OSErr            myErr = noErr;
  314.     Handle            myResourceHandle = nil;
  315.     
  316.     // open the language model resource from the resource fork
  317.     myResourceHandle = GetResource(kLMResourceType, kLMResourceID);
  318.     if (!myResourceHandle) 
  319.         return(ResError());
  320.     
  321.     // convert language model resource to a language model
  322.     myErr = SRNewLanguageObjectFromHandle(gSystem, &gVRLM, myResourceHandle);
  323.     return(myErr);
  324. }
  325.  
  326.  
  327. /////
  328. //
  329. // GoDirByDegrees
  330. // move a given number of degrees in a given direction
  331. // return value: TRUE if a movement was made; FALSE if no movement was made
  332. //
  333. /////
  334.  
  335. Boolean GoDirByDegrees (QTVRInstance theInstance, long theDir, long theAmt)
  336. {
  337.     float    myAngle;
  338.     Boolean    isMoved = false;
  339.     
  340.     QTVRSetAngularUnits(theInstance, kQTVRDegrees);
  341.     switch (theAmt) {     // convert the constant to a number of degrees; yuck!
  342.         case kAng45:
  343.             theAmt = 45.0;
  344.             break;
  345.         case kAng90    :
  346.             theAmt = 90.0;
  347.             break;
  348.         case kAng135:
  349.             theAmt = 135.0;
  350.             break;
  351.         case kAng180:
  352.             theAmt = 180.0;
  353.             break;
  354.         case kAng225:
  355.             theAmt = 225.0;
  356.             break;
  357.         case kAng270:
  358.             theAmt = 270.0;
  359.             break;
  360.         case kAng315:
  361.             theAmt = 315.0;
  362.             break;
  363.         case kAng10:
  364.             theAmt = 10.0;
  365.             break;
  366.         case kAng36:
  367.             theAmt = 36.0;
  368.             break;
  369.         case kUndefinedDegrees:
  370.             theAmt = 5.0;
  371.             break;
  372.         default:
  373.             theAmt = 10.0;
  374.             break;
  375.     }
  376.     
  377.     switch (theDir) {
  378.         case kDirUp:
  379.             myAngle = QTVRGetTiltAngle(theInstance);
  380.             QTVRSetTiltAngle(theInstance, myAngle + theAmt);
  381.             break;
  382.             
  383.         case kDirDown:
  384.             myAngle = QTVRGetTiltAngle(theInstance);
  385.             QTVRSetTiltAngle(theInstance, myAngle - theAmt);
  386.             break;
  387.             
  388.         case kDirLeft:
  389.             myAngle = QTVRGetPanAngle(theInstance);
  390.             QTVRSetPanAngle(theInstance, myAngle + theAmt);
  391.             break;
  392.             
  393.         case kDirRight:
  394.             myAngle = QTVRGetPanAngle(theInstance);
  395.             QTVRSetPanAngle(theInstance, myAngle - theAmt);
  396.             break;
  397.             
  398.         default:
  399.             break;
  400.     }
  401.  
  402.     QTVRUpdate(theInstance, kQTVRStatic);
  403.     
  404.     // determine whether a movement actually occurred
  405.     switch (theDir) {
  406.         case kDirUp:
  407.         case kDirDown:
  408.             isMoved = (myAngle != QTVRGetTiltAngle(theInstance));
  409.             break;
  410.         case kDirLeft:
  411.         case kDirRight:
  412.             isMoved = (myAngle != QTVRGetPanAngle(theInstance));
  413.             break;
  414.         default:
  415.             break;
  416.     }
  417.     
  418.     return(isMoved);
  419. }
  420.  
  421.  
  422. /////
  423. //
  424. // GoDirByRadians
  425. // move a given number of radians in a given direction
  426. // return value: TRUE if a movement was made; FALSE if no movement was made
  427. //
  428. /////
  429.  
  430. Boolean GoDirByRadians (QTVRInstance theInstance, long theDir, long theAmt)
  431. {
  432.     // convert radians to degrees, then call GoDirByDegrees
  433.     switch (theAmt) { 
  434.         case kRad1PiOver4:
  435.             theAmt = kAng45;
  436.             break;
  437.         case kRad2PiOver4:
  438.             theAmt = kAng90;
  439.             break;
  440.         case kRad3PiOver4:
  441.             theAmt = kAng135;
  442.             break;
  443.         case kRad4PiOver4:
  444.             theAmt = kAng180;
  445.             break;
  446.         case kRad5PiOver4:
  447.             theAmt = kAng225;
  448.             break;
  449.         case kRad6PiOver4:
  450.             theAmt = kAng270;
  451.             break;
  452.         case kRad7PiOver4:
  453.             theAmt = kAng315;
  454.             break;
  455.         default:
  456.             theAmt = kAng10;
  457.             break;
  458.     }
  459.     
  460.     return(GoDirByDegrees(theInstance, theDir, theAmt));
  461. }
  462.  
  463.  
  464. /////
  465. //
  466. // ZoomInOrOut
  467. // zoom in or out
  468. //
  469. /////
  470.  
  471. void ZoomInOrOut (QTVRInstance theInstance, long theDir)
  472. {
  473.     float    myFloat;
  474.     
  475.     myFloat = QTVRGetFieldOfView(theInstance);
  476.     switch (theDir) {
  477.         case kDirIn:
  478.             myFloat = myFloat / 2.0; 
  479.             break;
  480.         case kDirOut:
  481.             myFloat = myFloat * 2.0; 
  482.             break;
  483.         default:
  484.             break;
  485.     }
  486.     QTVRSetFieldOfView(theInstance, myFloat);    
  487.     QTVRUpdate(theInstance, kQTVRStatic);
  488. }
  489.  
  490.  
  491. /////
  492. //
  493. // MySpinTask
  494. // move in the desired direction, then re-prime the timer task
  495. // (here we just set an app global to alert code in event loop to do the move)
  496. //
  497. /////
  498.  
  499. pascal void MySpinTask (MyTMTaskPtr theTaskPtr)
  500. {
  501.     gDoSpeechTask = true;
  502.     PrimeTime((QElemPtr)theTaskPtr, theTaskPtr->theSpinDelay);
  503. }
  504.  
  505.  
  506. /////
  507. //
  508. // DoEventLoopSpinCheck
  509. // see whether a spin task is active, and respond appropriately
  510. //
  511. /////
  512.  
  513. void DoEventLoopSpinCheck (void)
  514. {
  515.     if (gDoSpeechTask) {
  516.         if (!GoDirByDegrees(gTMTaskRec.theInstance, gTMTaskRec.theSpinDir, gTMTaskRec.theSpinAmt)) {
  517.             StopSpinning();    
  518.         }
  519.         gDoSpeechTask = false;
  520.     }
  521. }
  522.  
  523.  
  524. /////
  525. //
  526. // IsSpinning
  527. // is the spinning task installed?
  528. //
  529. /////
  530.  
  531. Boolean IsSpinning (void)
  532. {
  533.     return(gTMTaskRec.theTMTask.qType && kTMTaskActive);
  534. }
  535.  
  536.  
  537. /////
  538. //
  539. // StartSpinning
  540. // start spinning in a given direction
  541. //
  542. /////
  543.  
  544. void StartSpinning (QTVRInstance theInstance, long theDir)
  545. {
  546.     // first we should check that our task isn't already installed;
  547.     // if it is, remove it (and then continue to install new task)
  548.     if (IsSpinning()) {
  549.         StopSpinning();
  550.     }
  551.         
  552.     // install a Time Manager task that periodically moves a small amount (5 degrees)
  553.     gTMTaskRec.theTMTask.tmAddr = gTimerTaskUPP;
  554.     gTMTaskRec.theTMTask.tmWakeUp = 0;
  555.     gTMTaskRec.theTMTask.tmReserved = 0;
  556.     gTMTaskRec.theInstance = theInstance;
  557.     gTMTaskRec.theSpinDir = theDir;
  558.     gTMTaskRec.theSpinAmt = kUndefinedDegrees;
  559.     gTMTaskRec.theSpinDelay = kSpinMillisecsDelay;
  560.  
  561.     InsXTime((QElemPtr) &gTMTaskRec);
  562.     PrimeTime((QElemPtr) &gTMTaskRec, kSpinMillisecsDelay);
  563. }
  564.  
  565.  
  566. /////
  567. //
  568. // StopSpinning
  569. // stop spinning: remove the Time Manager task
  570. //
  571. /////
  572.  
  573. void StopSpinning (void)
  574. {
  575.     RmvTime((QElemPtr) &gTMTaskRec);
  576. }
  577.  
  578.  
  579. /////
  580. //
  581. // InstallSpeechFeedbackRoutine
  582. // Set up QTVR intercept routines to do some speech.
  583. //
  584. /////
  585.  
  586. void InstallSpeechFeedbackRoutine (QTVRInstance theInstance)
  587. {
  588.     QTVRInterceptUPP theInterceptProc;
  589.     
  590.     theInterceptProc = NewQTVRInterceptProc(SpeechFeedbackRoutine);    
  591.     
  592.     //We'll just use the same intercept proc for each intercepted procedure.
  593.     QTVRInstallInterceptProc(theInstance, kQTVRSetPanAngleSelector, theInterceptProc, 0, 0);
  594.     QTVRInstallInterceptProc(theInstance, kQTVRSetTiltAngleSelector, theInterceptProc, 0, 0);
  595.     QTVRInstallInterceptProc(theInstance, kQTVRSetFieldOfViewSelector, theInterceptProc, 0, 0);
  596.     QTVRInstallInterceptProc(theInstance, kQTVRTriggerHotSpotSelector, theInterceptProc, 0, 0);
  597. }
  598.  
  599.  
  600. /////
  601. //
  602. // SpeechFeedbackRoutine
  603. //
  604. /////
  605.  
  606. #define FloatToFixed(a) ((Fixed)((float)(a) * fixed1))
  607. #define VRPi (3.1415926535898)
  608. #define VRDegreesToRadians(a) ((a) * VRPi / 180.0)
  609. #define VRRadiansToDegrees(a) ((a) * 180.0 / VRPi)
  610.  
  611. pascal void SpeechFeedbackRoutine (QTVRInstance theInstance, QTVRInterceptPtr theMsg, SInt32 refCon, Boolean *cancel)
  612. {
  613. #pragma unused(refCon)
  614.  
  615.     Str255    myCaption;
  616.     Boolean myCancelInterceptedProc = false;            // true == do NOT call thru; false == call thru
  617.     float    myAngle, *myAnglePtr;
  618.     
  619.     switch (theMsg->selector) {
  620.         case kQTVRSetTiltAngleSelector:    
  621.         case kQTVRSetPanAngleSelector:
  622.         case kQTVRSetFieldOfViewSelector:
  623.             myAnglePtr = (float *)theMsg->parameter[0];
  624.             myAngle = *myAnglePtr;                        //this is always in radians!
  625.             myAngle = VRRadiansToDegrees(myAngle);
  626.             NumToString(Fix2Long(FloatToFixed(myAngle)), myCaption);            
  627.             QTVRCallInterceptedProc(theInstance, theMsg);
  628.             SpeakString(myCaption);
  629.             myCancelInterceptedProc = true;
  630.             break;
  631.             
  632.         case kQTVRTriggerHotSpotSelector:                // get the hot spot ID and speak it    
  633.             NumToString((long)theMsg->parameter[0], myCaption);            
  634.             SRSpeakAndDrawText(gRecognizer, &myCaption[1], myCaption[0]);
  635.             break;
  636.             
  637.         default:
  638.             break;
  639.     }
  640.     
  641.     *cancel = myCancelInterceptedProc;
  642. }
  643.  
  644.  
  645. /////
  646. //
  647. // SpeakNameOfNode
  648. // A sample node-entering procedure; we just welcome the user to the new node.
  649. //
  650. /////
  651.  
  652. pascal OSErr SpeakNameOfNode (QTVRInstance theInstance, long nodeID, SInt32 refCon)
  653. {
  654. #pragma unused(refCon)
  655.  
  656.     Str255        myString;
  657.     OSErr        myErr = noErr;
  658.     
  659.     myErr = QTVRUtils_GetNodeName(theInstance, nodeID, &myString[0]);
  660.     if (!myErr) {
  661.         SpeakString("\p[[emph +]]Welcome [[emph -]] two");
  662.         while (SpeechBusy())
  663.             ;
  664.         SpeakString(myString);
  665.     }
  666.         
  667.     return(myErr);
  668. }
  669.  
  670.